# -*- coding: utf-8 -*-
#===================================================
# @Time : 2021/3/11 14:21                           
# @Author : weni09                                  
# @File : api.py
#===================================================

from fastapi import APIRouter, Query,Depends,Body
from starlette.datastructures import UploadFile
from tortoise.contrib.pydantic import pydantic_model_creator
from typing import Optional
from fastapi.requests import Request
from tortoise.functions import Count,Q,Max,Min
from .models import Article,Comment,Tag,About
from admin.models import User
from .response import ApiResponse,ApiResponseBase,ApiResponseAuth,ApiResponseBaseAuth
from admin.fastapi_admin.depends import get_model
from .schemas import ArticleViewsUpdate,CommentAdd,ArticleCreate,AboutBase,UserRegister
from utils.MyHashEncryptor import MyHashEncryptor as MyHash
from tortoise import transactions
from .Depends import get_user,gen_rnd_filename
from setting import BASE_DIR,IMAGE_COUNT,UPLOAD_FILE_NAME,STATIC_PATH
import os,setting
from datetime import datetime
from admin.context_handler import update_slug
from tortoise.exceptions import IntegrityError
from admin.fastapi_admin.depends import jwt_required
from admin.token import token
router = APIRouter()

@router.get("/res/{resource}")
async def get_resource(
        resource:str,
        limit:Optional[int]=100,
        offset:Optional[int]=0,
        model = Depends(get_model),
        user =Depends(get_user),
):
    """
    查询标签/分类对应的文章
    :param resource:
    :param limit:
    :param offset:
    :param model:
    :param user:
    :return:
    """
    res = resource.lower()
    if res not in ('tag','category'):
        return ApiResponse(code="failed",msg="query object error.")
    qs = model.all()
    ##针对不同用户过滤数据
    if user is None:
        qs = qs.filter(articles__is_public=True)
    elif user.is_superuser:
        qs = qs.all()
    else:
        qs = qs.filter(Q(articles__is_public=True) | Q(articles__author_id=user.id))

    total = await qs.count()
    data = await qs.annotate(art_total=Count('articles__id')).order_by('id')\
        .offset(offset).limit(limit).values('id', 'name', 'slug', 'art_total')

    user_id = user.id if user else 0

    return ApiResponseAuth(total=total,data=data,user_id=user_id)

@router.get("/article_list")
async def get_articles(
        res:Optional[str]=Query(None),
        slug:Optional[str]=Query(None),
        limit:Optional[int]=10,
        offset:Optional[int]=0,
        sort:Optional[str]='-create_date',
        user=Depends(get_user),

):
    sort = ('-is_top',sort)
    if res and slug:
        model = get_model(res)
        modelInstance = await model.filter(slug=slug).get_or_none()
        qs = modelInstance.articles.all().order_by(*sort).offset(offset).limit(limit)
    else:
        qs = Article.all().order_by(*sort).limit(limit).offset(offset)
    if user is None:
        qs = qs.filter(is_public=True)
    elif user.is_superuser:
        qs = qs.all()
    else:
        qs = qs.filter(Q(is_public=True) | Q(author_id=user.id))
    data =  await qs.annotate(comment_cnt=Count('comments__id'),category_name=Max('category__name'),\
                        author_username=Max('author__username'),author_avatar=Max('author__avatar'), \
                        nickname = Max('author__nickname'),category_slug=Max('category__slug'),)\
                    .values('id','title','summary','is_public','is_top','img_link',\
                      'create_date','update_date','views','slug','category_name',\
                      'author_username','author_avatar','comment_cnt',\
                      'nickname','category_slug')

    user_id = user.id if user else 0
    total = await qs.count()
    data = {} if data == [] or data == '{}' else data
    return ApiResponseAuth(total= 0 if total<0 else total,data=data,user_id=user_id)


@router.get('/comment/{art_id}')
async def get_comment(
        art_id:int
):
    comment = Comment.filter(article_id = art_id).all()
    total = await comment.count()
    commentUserCnt = len(await comment.distinct().values('email'))
    comments = await comment.values('id','content','content_md','parent_id',\
                            'root_id','nickname','email','create_date',author_name='article__author__username')
    # commentsRoot = map(lambda x:{"root":x if x.root_id==0, },comments)
    node=[]
    for comment in comments:
        if comment['root_id'] == 0:
            comment.update(children=list(filter(lambda x: x['root_id'] == comment['id'], comments)))
            node.append(comment)
    # print(node)
    data={"comments":node,"comment_usercnt":commentUserCnt}
    return ApiResponse(total=total,data=data)

@router.get('/artDetail/{id}')
async def get_article(
    id:int,
    user=Depends(get_user)
):
    qs = Article.filter(id=id)

    if user is None:
        qs = qs.filter(is_public=True).get_or_none()
    elif user.is_superuser:
        qs = qs.get_or_none()
    else:
        qs = qs.filter(Q(is_public=True) | Q(author_id=user.id)).get_or_none()

    tags = await qs.values('tags__id','tags__name','tags__slug')
    if not tags or not tags[0]['tags__name']:
        tags=[]
    data = await qs.values('id','title','summary','body','body_md','is_public','is_top','img_link','create_date',\
                           'update_date','views','author_id','author__username','author__avatar','category_id',\
                           'category__name','category__slug',nickname='author__nickname')
    if data:
        data = data[0]
        data.update(tags=tags)
        data["img_link"] = "" if not data["img_link"] else data["img_link"]
    user_id = user.id if user else 0
    return ApiResponseBaseAuth(data=data,user_id=user_id)

@router.post('/artViews/update')
async def update_views(
    request:Request,
    data:ArticleViewsUpdate
):
    data = data.dict()
    ip = request.client.host
    #data + ip md5后作为key存入redis
    key_md = MyHash.myencryped('md5',str(data) + ip)
    # 检查key存在，存在返回失败。
    if await request.app.state.redis.exists(key_md):
        return ApiResponseBase(code="failed",msg="updated too fast")
    else:
        await request.app.state.redis.set(key_md,0)
        await request.app.state.redis.expire(key_md,5*60)
    idN,viewsN = data.get('id'),data.get('views')
    # print(data,idN,viewsN)
    try:
        article = Article.filter(pk=idN)
        # print(await article)

        oldViews = await article.values('views')
        newViews = int(oldViews[0]['views']) + viewsN

        await Article.filter(pk=idN).update(views=newViews)
        data = await Article.get(pk=idN).values('id','views')
        data[0].update(oldViews=oldViews[0].get('views'))
    # print(data[0])
    except Exception as e:
        return ApiResponseBase(code="exception",msg=str(e))
    return ApiResponseBase(data=data[0])

@transactions.atomic
@router.post("/comment/add")
async def add_comment(
    request:Request,
    newComment: CommentAdd
):
    newComment = newComment.dict()
    key_md = MyHash.myencryped('md5',str(newComment))
    try:
        if await request.app.state.redis.exists(key_md):
            return ApiResponseBase(code="failed", msg="Please do not repeat the submission")
        else:
            await request.app.state.redis.set(key_md, 0)
            await request.app.state.redis.expire(key_md, 60 * 60)

        comment = await Comment(**newComment)
        articleInstance = await Article.get(id=newComment.get('article_id'))
        comment.article = articleInstance
        await comment.save()
        # data = await comment.get().values('id','parent_id','create_date','article_id',article_title='article__title')
        creator = pydantic_model_creator(Comment,include=('id','parent_id','root_id','create_date','article_id'))
    except Exception as e:
        return ApiResponseBase(code="exception",msg=str(e))
    return ApiResponseBase(data=creator.from_orm(comment).dict())


@router.post("/file/upload",dependencies=[Depends(jwt_required)])
async def recv_file(*,request:Request):
    """
    文件上传接口
    :param request:
    :return:
    """
    data = []
    form_data = await request.form()
    keys = list(form_data.keys())
    if len(keys) > 1 or keys[0] not in UPLOAD_FILE_NAME:
        return ApiResponseBase(code="failed", msg='FILE_KEY_ERROR')

    files = list(filter(lambda x: isinstance(x,UploadFile),form_data.values()))
    # files = [file for file in list(form_data.values() if isinstance(file, UploadFile)]
    print(files)
    if not files:
        return ApiResponseBase(code="failed", msg='NO_IMG')
    if len(files) > IMAGE_COUNT:
        return ApiResponseBase(code="failed", msg='IMG_COUNT_OVER')

    file_dir = os.path.join(STATIC_PATH,str(keys[0]),str(datetime.now().year),str(datetime.now().strftime("%m")),str(datetime.now().strftime("%d")))

    # file_dir = os.path.join(IMAGE_PATH,str(datetime.now().year),str(datetime.now().strftime("%m")),str(datetime.now().strftime("%d")))
    file_abs_dir = os.path.join(BASE_DIR,file_dir)
    if not os.path.exists(file_abs_dir):
        try:
            os.makedirs(file_abs_dir)
        except Exception:
            return ApiResponseBase(code="failed",msg = 'ERROR_CREATE_DIR')
    # url 主要地址,静态文件服务地址
    url_domain = request.base_url.__str__()
    url_domain += '/' if url_domain[-1] != '/' else ''
    url_domain=''
    err, url, alt = "", "", ""
    for file in files:
        file_data = file.file.read()
        if len(file_data) > getattr(setting,keys[0].upper() + "_SIZE"):
            return ApiResponseBase(code="failed", msg='IMAGE_OVER_LARGE')

        prefix = f"{gen_rnd_filename()}_" # 前缀
        filepath = os.path.join(file_abs_dir,prefix + file.filename) # 文件绝对路径
        # 批量文件写入
        try:
            with open(filepath,'wb') as f:
                f.write(file_data)
            url = url_domain + file_dir.replace('\\','/') + "/" + prefix + file.filename
            alt = prefix + file.filename
            data.append(dict(url=url,alt=alt))
        except Exception as e:
            err = str(e)
        finally:
            file.file.close()
            f.close()
    if err:
        return ApiResponseBase(code="exception", msg=err)
    if len(data) == 0:
        return ApiResponseBase(code="failed", msg='WRITE_ALL_FAILED')
    if data and len(data) < len(files):
        return ApiResponseBase(code="failed", msg='WRITE_SCOPE_FAILED')
    data = data[0] if len(data) == 1 else data
    return ApiResponseBase(data=data)

@router.post("/file/delete",dependencies=[Depends(jwt_required)])
async def delete_file(path:str=Body(...,embed=True,min_length=1)):
    abs_path = os.path.join(BASE_DIR,path)
    if os.path.exists(abs_path):
        try:
            os.remove(abs_path)
        except Exception:
            return ApiResponseBase(code="exception", msg='FILE_DELETE_FAILED')
    else:
        return ApiResponseBase(code="failed", msg='FILE_NOT_EXIST')
    return ApiResponseBase(msg="deleted successfully.",data=dict(path=path))

@transactions.atomic
@router.post("/article/edit",dependencies=[Depends(jwt_required)])
async def add_article(
    articleCreate: ArticleCreate

):
    newArticle = articleCreate.dict()
    artId = newArticle.get('art_id')

    if not artId:
        await update_slug(newArticle,Article)  #文章添加slug
    tags = list(filter(lambda x: isinstance(x,int) == True,newArticle.get('selectedTags')))
    needAddTags = list(filter(lambda x: isinstance(x,int) == False,newArticle.get('selectedTags')))
    if needAddTags:
        for i in range(len(needAddTags)):
            needAddTagDict = {"name":needAddTags[i]}
            await update_slug(needAddTagDict,Tag) #标签添加slug
            # needAddTagObj = Tag(**needAddTagDict)
            # print(needAddTagObj.name)
            try:
                needAddTags[i] = await Tag.create(**needAddTagDict)
            except IntegrityError as e:
                if str(e).find("duplicate") != -1:
                    needAddTags[i] = await Tag.filter(name=needAddTags[i]).get_or_none()
                else:
                    return ApiResponseBase(code="exception", msg=str(e))
    # print(tags,needAddTags)
    # try:
    tags = await Tag.filter(pk__in=tags)
    tags.extend(needAddTags)
    if not artId :
        AddedArticle = await Article.create(**newArticle)
    else:
        newArticle.pop('selectedTags')
        newArticle.pop('art_id')
        newArticle.update(update_date=datetime.now())
        await Article.filter(pk=artId).update(**newArticle)
        AddedArticle = await Article.get(pk=artId)
    await AddedArticle.tags.add(*tags)
    creator = pydantic_model_creator(Article,include=('id','title','create_date'))
    # except Exception as e:
    #     raise e
        # return ApiResponseBase(code="exception",msg=str(e))
    return ApiResponseBase(data=creator.from_orm(AddedArticle).dict())



@router.post("/about/edit",dependencies=[Depends(jwt_required)])
async def edit_about(
    newAbout:AboutBase,
    user=Depends(get_user)
):
    if not user.is_superuser:
        return ApiResponseBase(code="failed",msg="Sorry,You don't have permission to update this")

    newAbout = newAbout.dict()
    aboutId = newAbout.get('id')
    if await About.get_or_none(pk=aboutId):
        newAbout.pop('id')
        await About.filter(pk=aboutId).update(**newAbout)
    else:
        await About.create(**newAbout)
    return ApiResponseBase(data=dict(id=aboutId))

@router.get("/about/query/{id}")
async def query_about(
    id:int
):
    about = await About.get_or_none(pk=id).values('id','content','content_md')
    data = about[0]
    return ApiResponseBase(data=data)

import re
@router.post('/register')
async def add_user(
    newUser:UserRegister
):
    newUser = newUser.dict()
    newUser["password"] = token.get_password_hash(newUser["password"])
    if not re.match(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', newUser["username"]):
        return ApiResponseBase(code="failed",msg="ERROR_EMAIL_ADDRESS")
    try:
        user = await User.create(**newUser)
    except Exception as e:
        if re.match(r'^duplicate[\s\S]+nickname[\s\S]+exists.$',str(e)):
            return ApiResponseBase(code="failed",msg="NICKNAME_ALREADY_EXISTS")
        if re.match(r'^duplicate[\s\S]+username[\s\S]+exists.$',str(e)):
            return ApiResponseBase(code="failed", msg="USERNAME_ALREADY_EXISTS")
        return ApiResponseBase(code="failed", msg=str(e))
    creator = pydantic_model_creator(User, include=('id','username','nickname','created_at'))
    return ApiResponseBase(data=creator.from_orm(user).dict())
